package com.ming.common.beetl.dynamic;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import javax.tools.JavaFileObject.Kind;

import com.ming.common.beetl.entity.BaseEntity;
import org.beetl.core.GroupTemplate;
import org.beetl.core.misc.ByteClassLoader;
import org.beetl.sql.core.SQLManager;
import org.beetl.sql.gen.SourceBuilder;
import org.beetl.sql.gen.SourceConfig;
import org.beetl.sql.gen.simple.EntitySourceBuilder;
import org.beetl.sql.gen.simple.StringOnlyProject;

public class DynamicEntityLoader<T> {
    protected SQLManager sqlManager;
    protected Map<String, Class<? extends T>> cache;
    private final Pattern CLASS_PATTERN;
    private static Map<String, JavaFileObject> fileObjectMap = new ConcurrentHashMap();
    protected String pkg;
    protected Class<T> baseClass;
    protected ByteClassLoader loader;

    public DynamicEntityLoader(SQLManager sqlManager) {
        this(sqlManager, "com.test001", (Class<T>) BaseEntity.class);
    }

    public DynamicEntityLoader(SQLManager sqlManager, String pkg, Class<T> clazz) {
        this.cache = new ConcurrentHashMap();
        this.CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s*");
        this.loader = null;
        this.sqlManager = sqlManager;
        this.pkg = pkg;
        this.baseClass = clazz;
        ClassLoader defaultClassLoader = Thread.currentThread().getContextClassLoader() != null ? Thread.currentThread().getContextClassLoader() : GroupTemplate.class.getClassLoader();
        this.loader = new ByteClassLoader(defaultClassLoader);
    }

    public DynamicEntityLoader(SQLManager sqlManager, String pkg, Class<T> clazz, ClassLoader classLoader) {
        this.cache = new ConcurrentHashMap();
        this.CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s*");
        this.loader = null;
        this.sqlManager = sqlManager;
        this.pkg = pkg;
        this.baseClass = clazz;
        this.loader = new ByteClassLoader(classLoader);
    }

    public Class<? extends T> getDynamicEntity(String table) {
        return this.getDynamicEntity(table, this.baseClass);
    }

    public Class<? extends T> getDynamicEntity(String table, Class<T> clazz) {
        Class<? extends T> c = (Class)this.cache.get(table);
        if (c != null) {
            return c;
        } else {
            c = (Class)this.cache.computeIfAbsent(table, (s) -> {
                Class<? extends T> newCLass = this.compile(s, clazz.getName());
                return newCLass;
            });
            return c;
        }
    }

    protected Class<? extends T> compile(String table, String baseObject) {
        List<SourceBuilder> sourceBuilder = new ArrayList();
        SourceBuilder entityBuilder = new EntitySourceBuilder();
        sourceBuilder.add(entityBuilder);
        SourceConfig config = new SourceConfig(this.sqlManager, sourceBuilder);
        config.setEntityParentClass(baseObject);
        StringOnlyProject project = new StringOnlyProject() {
            public String getBasePackage(String sourceBuilderName) {
                return DynamicEntityLoader.this.pkg + "." + sourceBuilderName;
            }
        };
        String entityPkg = this.pkg + ".entity";
        config.gen(table, project);
        String javaCode = project.getContent();
        /*javaCode = javaCode.replace("import org.beetl.sql.annotation.entity.*;","import org.beetl.sql.annotation.entity.AutoID;" + System.lineSeparator() +
                "import org.beetl.sql.annotation.entity.Table;" + System.lineSeparator() +
                "import com.ming.common.beetl.entity.BaseEntity;"+ System.lineSeparator());*/
        Class<? extends T> c = this.doCompile(entityPkg, javaCode);
        return c;
    }

    protected Class<? extends T> doCompile(String pkg, String javaCode) {
        Matcher matcher = this.CLASS_PATTERN.matcher(javaCode);
        if (matcher.find()) {
            String className = matcher.group(1);
            byte[] classByte = this.getByte(pkg, className, javaCode);
            Class var6 = this.loader.defineClass(pkg + "." + className, classByte);
            return var6;
        } else {
            throw new IllegalArgumentException("No valid class");
        }
    }

    protected byte[] getByte(String pkg, String className, String javaCode) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> compileCollector = new DiagnosticCollector();
        JavaFileManager javaFileManager = new DynamicEntityLoader.TmpJavaFileManager(compiler.getStandardFileManager(compileCollector, (Locale)null, (Charset)null));
        JavaFileObject sourceJavaFileObject = new DynamicEntityLoader.TmpJavaFileObject(className, javaCode);
        Boolean result = compiler.getTask((Writer)null, javaFileManager, compileCollector, (Iterable)null, (Iterable)null, Arrays.asList(sourceJavaFileObject)).call();
        if (!result) {
            throw new IllegalArgumentException("compile error " + compileCollector.getDiagnostics());
        } else {
            JavaFileObject bytesJavaFileObject = (JavaFileObject)fileObjectMap.get(pkg + "." + className);
            byte[] bs = ((DynamicEntityLoader.TmpJavaFileObject)bytesJavaFileObject).getCompiledBytes();
            return bs;
        }
    }

    public static class TmpJavaFileObject extends SimpleJavaFileObject {
        private String source;
        private ByteArrayOutputStream outputStream;

        public TmpJavaFileObject(String name, String source) {
            super(URI.create("String:///" + name + Kind.SOURCE.extension), Kind.SOURCE);
            this.source = source;
        }

        public TmpJavaFileObject(String name, JavaFileObject.Kind kind) {
            super(URI.create("String:///" + name + Kind.SOURCE.extension), kind);
            this.source = null;
        }

        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            if (this.source == null) {
                throw new IllegalArgumentException("source == null");
            } else {
                return this.source;
            }
        }

        public OutputStream openOutputStream() throws IOException {
            this.outputStream = new ByteArrayOutputStream();
            return this.outputStream;
        }

        public byte[] getCompiledBytes() {
            return this.outputStream.toByteArray();
        }
    }

    public static class TmpJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
        protected TmpJavaFileManager(JavaFileManager fileManager) {
            super(fileManager);
        }

        public JavaFileObject getJavaFileForInput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind) throws IOException {
            JavaFileObject javaFileObject = (JavaFileObject) DynamicEntityLoader.fileObjectMap.get(className);
            return javaFileObject == null ? super.getJavaFileForInput(location, className, kind) : javaFileObject;
        }

        public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
            JavaFileObject javaFileObject = new DynamicEntityLoader.TmpJavaFileObject(className, kind);
            DynamicEntityLoader.fileObjectMap.put(className, javaFileObject);
            return javaFileObject;
        }
    }
}
